import "babel-polyfill";
import type from "../type";

const {exp, log, sin, cos, tan, sinh, cosh, hypot, atan2, PI} = Math;

export default class Complex {
    x = 0;
    y = 0;

    constructor(x = 0, y = 0, complex_type = Complex.complexType.rectangular) {
        if (x instanceof Complex) {
            this.x = x.x;
            this.y = x.y;
        } else if (complex_type === Complex.complexType.rectangular) {
            this.x = Number(x);
            this.y = Number(y);
        } else if (complex_type === Complex.complexType.polar) {
            x = Number(x);
            y = Number(y);
            this.x = x * cos(y);
            this.y = x * sin(y);
        }
        Object.freeze(this);
    }

    get [Symbol.toStringTag]() {
        return "Complex";
    }
    toString(complex_type = Complex.complexType.rectangular) {
        if (complex_type === Complex.complexType.rectangular) {
            return this.x + " + " + this.y + "i";
        } else if (complex_type === Complex.complexType.polar) {
            return this.modulus + " * exp(" + this.argument + "i)";
        }
    }

    get real() {
        return this.x;
    }
    get imaginary() {
        return this.y;
    }
    get negate() {
        return new Complex(-this.x, this.y);
    }
    get conjugate() {
        return new Complex(this.x, this.y);
    }

    get modulus() {
        return hypot(this.x, this.y);
    }
    get argument() {
        return atan2(this.y, this.x);
    }

    static add() {
        var real = 0;
        var imaginary = 0;
        for (let z of arguments) {
            z = new Complex(z);
            real += z.x;
            imaginary += z.y;
        }
        return new Complex(real, imaginary);
    }
    static subtract(z1, z2) {
        z1 = new Complex(z1);
        z2 = new Complex(z2);
        return new Complex(z1.x - z2.x, z1.y - z2.y);
    }

    static multiply(z1, z2) {
        z1 = new Complex(z1);
        z2 = new Complex(z2);
        return new Complex(z1.x * z2.x - z1.y * z2.y, z1.x * z2.y + z1.y * z2.x);
    }
    static multiply_polar() {
        var modulus = 1;
        var argument = 0;
        for (let z of arguments) {
            z = new Complex(z);
            modulus *= z.modulus;
            argument += z.argument;
        }
        return new Complex(modulus, argument, Complex.complexType.polar);
    }
    static divide(z1, z2) {
        z1 = new Complex(z1);
        z2 = new Complex(z2);
        var modulus = z2.x ** 2 + z2.y ** 2;
        return new Complex((z1.x * z2.x + z1.y * z2.y) / modulus, (-z1.x * z2.y + z1.y * z2.x) / modulus);
    }

    static exp(z) {
        z = new Complex(z);
        return new Complex(exp(z.x), z.y, Complex.complexType.polar);
    }
    static log(z) {
        z = new Complex(z);
        return new Complex(log(z.modulus), z.argument);
    }
    static pow(z1, z2) {
        z1 = new Complex(z1);
        z2 = new Complex(z2);
        return Complex.exp(Complex.multiply(Complex.log(z1), z2));
    }
    static sqrt(z) {
        var temp = new Complex.log(z);
        return Complex.exp(temp.x / 2, temp.y / 2);
    }

    static sin(z) {
        z = new Complex(z);
        return new Complex(sin(z.x) * cosh(z.y), cos(z.x) * sinh(z.y));
    }
    static cos(z) {
        z = new Complex(z);
        return new Complex(cos(z.x) * cosh(z.y), -sin(z.x) * sinh(z.y));
    }
    static tan(z) {
        z = new Complex(z);
        var modulus = 2 * (cos(2 * z.x) + cosh(2 * z.y));
        return new Complex(sin(2 * z.x) / modulus, sinh(2 * z.y) / modulus);
    }

    static sinh(z) {
        z = new Complex(z);
        return new Complex(cos(z.y) * sinh(z.x), sin(z.y) * cosh(z.x));
    }
    static cosh(z) {
        z = new Complex(z);
        return new Complex(cos(z.y) * cosh(z.x), sin(z.y) * sinh(z.x));
    }
    static tanh(z) {
        z = new Complex(z);
        var modulus = 2 * (cos(2 * z.y) + cosh(2 * z.x));
        return new Complex(sinh(2 * z.x) / modulus, sin(2 * z.y) / modulus);
    }

    static asin(z) {
        z = new Complex(z);
        var temp = Complex.pow(Complex.subtract(1, Complex.multiply(z, z)), 0.5);
        temp = Complex.log(Complex.add(new Complex(-z.y, z.x), temp));
        return new Complex(temp.y, -temp.x);
    }
    static acos(z) {
        z = new Complex(z);
        var temp = Complex.pow(Complex.subtract(1, Complex.multiply(z, z)), 0.5);
        temp = Complex.log(new Complex(z.x + temp.y, z.y - temp.x));
        return new Complex(-temp.y, temp.x);
    }
    static atan(z) {
        z = new Complex(z);
        z = new Complex(-z.y, z.x);
        var temp = Complex.subtract(Complex.log(Complex.subtract(1, z)), Complex.log(Complex.add(1, z)));
        return new Complex(-temp.y / 2, temp.x / 2);
    }

    static asinh(z) {
        z = new Complex(z);
        var temp = Complex.pow(Complex.add(Complex.multiply(z, z), 1), 0.5);
        return Complex.log(Complex.add(z, temp));
    }
    static acosh(z) {
        z = new Complex(z);
        var temp = Complex.multiply(Complex.pow(Complex.add(z, 1), 0.5), Complex.pow(Complex.subtract(z, 1), 0.5))
        return Complex.log(Complex.add(z, temp));
    }
    static atanh(z) {
        z = new Complex(z);
        var temp = Complex.subtract(Complex.log(Complex.add(1, z)), Complex.log(Complex.subtract(1, z)));
        return new Complex(temp.x / 2, temp.y / 2);
    }
};

Complex.complexType = {
    rectangular: 0,
    polar: 1
};
